//
// Created by 邹迪凯 on 2021/7/20.
//

#ifndef LWPT_LIGHTPRIVATETASKASSIGNMENT_H
#define LWPT_LIGHTPRIVATETASKASSIGNMENT_H

#include "assignment.h"
#include "util.h"



class LPTAApplicationChunk : public Chunk {
public:
    string fid;
    string fl;
    string bnx;
    string bny;
    string lox;
    string loy;

    long size() const override {
        return fid.size() + fl.size() + bnx.size() + bny.size() + lox.size() + loy.size() + overhead();
    }

    long overhead() const {
        return 1;
    }
};

class LPTARegisterChunk : public Chunk {
public:
    string fl;
    string id;
    string bnx;
    string bny;
    string lox;
    string loy;

    long size() const override {
        return id.size() + bnx.size() + bny.size() + lox.size() + loy.size() + overhead();
    }

    long overhead() const {
        return 0;
    }
};

class LPTAWorker : public Worker {
public:
    vector<int> *idPrimeSuite;
    vector<int> *locationPrimeSuite;
    int s;

    LPTAWorker(string id, Location location, vector<int> &idPrimeSuite, vector<int> &locationPrimeSuite, int s)
            : Worker(
            id, location), idPrimeSuite(&idPrimeSuite), locationPrimeSuite(&locationPrimeSuite), s(s) {
    }

    Chunk *apply(Server &server) override {
        assert(s + 2 < location.lon.size());
        auto chunk = new LPTAApplicationChunk();
        auto strId = id;
        vector<string> *fid = computeFingerprint(strId.c_str(), strId.size(), *idPrimeSuite);
        chunk->fid = join(fid);
        auto millCoords = millierConversion(stod(location.lat), stod(location.lon));
        string x = to_string((int) millCoords[0]);
        string y = to_string((int) millCoords[1]);
        chunk->lox = x.substr(x.size() - s, s);
        chunk->loy = y.substr(y.size() - s, s);
        chunk->bnx = x.substr(x.size() - s - 1, 1);
        chunk->bny = y.substr(y.size() - s - 1, 1);
        string xyab = x.substr(0, x.size() - s - 1).append(y.substr(0, y.size() - s - 1));
        vector<string> *fl = computeFingerprint(xyab.c_str(), xyab.size(), *locationPrimeSuite);
        chunk->fl = join(fl);
        delete millCoords;
        return chunk;
    }
};

double fingerprintCodingDistance(LPTAApplicationChunk *worker, LPTARegisterChunk *task, int s) {
    double deltax = pow(10, s) * (stoi(worker->bnx) - stoi(task->bnx)) + stoi(worker->lox) - stoi(task->lox);
    double deltay = pow(10, s) * (stoi(worker->bny) - stoi(task->bny)) + stoi(worker->loy) - stoi(task->loy);
    return sqrt(deltax * deltax + deltay * deltay);
}

class LPTAServer : public Server {
public:
    vector<int> *idPrimeSuite;
    int primeCount;
    int s;
    double costAlpha;
    double costBeta;
    double gb;
    unordered_map<string, unordered_map<string, double> *> taskWorkerDistance;
    set<string> candidateTasks, candidateWorkers;
    unordered_map<string, LPTARegisterChunk *> registrationMap;
    unordered_map<string, vector<LPTAApplicationChunk *> *> cs;
    unordered_map<string, LPTAApplicationChunk *> applicationMap;
    unordered_map<string, double> wx, wy;
    unordered_map<string, string> cx, cy;
    unordered_map<string, bool> visx, visy;
    double delta;

    LPTAServer(int primeCount, vector<int> *idPrimeSuite, int s) : primeCount(primeCount), idPrimeSuite(idPrimeSuite),
                                                                   s(s) {
        costAlpha = 1;
        costBeta = 1;
        gb = 2000;
    }

    void prepareForAssignment() override {
        //candidate selection
        for (auto &ap : *applications) {
            auto a = static_cast<LPTAApplicationChunk *>(ap);
            applicationMap[a->fid] = a;
        }
        for (const auto &reg : *registrations) {
            auto task = static_cast<LPTARegisterChunk *>(reg);
            registrationMap[task->id] = task;
            if (cs.find(task->id) == cs.end()) {
                cs[task->id] = new vector<LPTAApplicationChunk *>;
            }
            for (const auto &ap : *applications) {
                auto worker = static_cast<LPTAApplicationChunk *>(ap);
//                if (worker->fl == task->fl && worker->bnx == task->bnx && worker->bny == task->bny) {
                    if (worker->fl == task->fl) {
                    cs[task->id]->push_back(worker);
                    candidateTasks.insert(task->id);
                    candidateWorkers.insert(worker->fid);
                }
            }
        }
    }

    void assignTask() override {
        //winner selection
        for (auto &taskCandidates : cs) {
            for (auto &candidate : *(taskCandidates.second)) {
                double d = fingerprintCodingDistance(candidate, registrationMap[taskCandidates.first], s);
                if (taskWorkerDistance.find(taskCandidates.first) == taskWorkerDistance.end()) {
                    taskWorkerDistance[taskCandidates.first] = new unordered_map<string, double>;
                }
                (*taskWorkerDistance[taskCandidates.first])[candidate->fid] = costAlpha * pow(d, costBeta);
            }
        }
        //把矩阵补足成方阵
        if (candidateWorkers.size() < candidateTasks.size()) {
            int dummyCount = candidateTasks.size() - candidateWorkers.size();
            for (int i = 0; i < dummyCount; i++) {
                string dummyWorkerId = generateId(ID_LENGTH, candidateWorkers);
                candidateWorkers.insert(dummyWorkerId);
                for (const auto &task : taskWorkerDistance) {
                    (*task.second)[dummyWorkerId] = costAlpha * pow(2 * gb, costBeta);
                }
            }
        } else if (candidateWorkers.size() > candidateTasks.size()) {
            int dummyCount = candidateWorkers.size() - candidateTasks.size();
            for (int i = 0; i < dummyCount; i++) {
                string dummyTaskId = generateId(ID_LENGTH, candidateTasks);
                candidateTasks.insert(dummyTaskId);
                taskWorkerDistance[dummyTaskId] = new unordered_map<string, double>;
                for (const auto &workerId : candidateWorkers) {
                    (*taskWorkerDistance[dummyTaskId])[workerId] = costAlpha * pow(2 * gb, costBeta);
                }
            }
        }
        for (auto &taskWorkers:taskWorkerDistance) {
            for (auto &cw:candidateWorkers) {
                if (taskWorkers.second->find(cw) == taskWorkers.second->end()) {
                    (*taskWorkers.second)[cw] = costAlpha * pow(2 * gb, costBeta);
                }
            }
        }
        for (auto &taskWorkers:taskWorkerDistance) {
            for (auto &workerDistance:*taskWorkers.second) {
                workerDistance.second = -workerDistance.second;
            }
        }

        km();

        for (auto &taskWorkers:taskWorkerDistance) {
            auto taskId = taskWorkers.first;
            if (!cx[taskId].empty() &&
                !equals(taskWorkerDistance[taskId]->at(cx[taskId]), -costAlpha * pow(2 * gb, costBeta))) {
                assignments.push_back(new pair<string, string>(taskId, cx[taskId]));
                assignedTaskCount++;
            }
        }

        //release the memory
        for (const auto &csj : cs) {
            delete csj.second;
        }
        for (const auto &item : taskWorkerDistance) {
            delete item.second;
        }
    }

    bool dfs(string tt) {
        visx[tt] = true;
        for (const auto &ww:candidateWorkers) {
            if (!visy[ww]) {
                double t = wx[tt] + wy[ww] - taskWorkerDistance[tt]->at(ww);
                if (equals(t, 0)) {
                    visy[ww] = true;
                    if (cy[ww].empty() || dfs(cy[ww])) {
                        cx[tt] = ww;
                        cy[ww] = tt;
                        return true;
                    }
                } else if (t > 0) {
                    delta = min(delta, t);
                }
            }
        }
        return false;
    }

    void km() {
        const string EMPTY_STR;
        memset(cx, candidateTasks, EMPTY_STR);
        memset(cy, candidateWorkers, EMPTY_STR);
        memset(wx, candidateTasks, 0.0);
        memset(wy, candidateWorkers, 0.0);
        for (const auto &t:candidateTasks) {
            for (const auto &w:candidateWorkers) {
                if (taskWorkerDistance[t]->find(w) == taskWorkerDistance[t]->end()) {
                    cout << w << endl;
                    cout << *candidateWorkers.find(w) << endl;
                }
                wx[t] = max(wx[t], taskWorkerDistance[t]->at(w));
            }
        }
        for (const auto &t:candidateTasks) {
            while (true) {
                delta = INF;
                memset(visx, candidateTasks, false);
                memset(visy, candidateWorkers, false);
                if (dfs(t))
                    break;
                for (const auto &tt:candidateTasks)
                    if (visx[tt])
                        wx[tt] -= delta;
                for (const auto &ww:candidateWorkers)
                    if (visy[ww])
                        wy[ww] += delta;

            }
        }
    }
};

class LPTATest : public Test {
public:
    vector<int> *idPrimeSuite;
    vector<int> *locationPrimeSuite;
    int s;

    LPTATest(DataSource *ds, int primeCount, int seed, int s) : Test(ds), s(s) {
        srand(seed);
        double *milGeos = millierConversion(stod(ds->allWorkers[0].lat), stod(ds->allWorkers[0].lon));
        int fullLength = to_string((int) milGeos[0]).append(to_string((int) milGeos[1])).size();
        int binaryLength = (fullLength - 2 * (s + 1)) * 8;
        cout << 2 * binaryLength * binaryLength << endl;
        auto primes = generatePrimeList(2 * binaryLength * binaryLength);
        idPrimeSuite = randomSelect(primes, primeCount);
        locationPrimeSuite = randomSelect(primes, primeCount);
        delete primes;
    }

    Chunk *createTaskRegistration(Task &task) override {
        auto chunk = new LPTARegisterChunk();
        chunk->id = task.id;
        auto millCoords = millierConversion(stod(task.location.lat), stod(task.location.lon));
        string x = to_string((int) millCoords[0]);
        string y = to_string((int) millCoords[1]);
        chunk->lox = x.substr(x.size() - s, s);
        chunk->loy = y.substr(y.size() - s, s);
        chunk->bnx = x.substr(x.size() - s - 1, 1);
        chunk->bny = y.substr(y.size() - s - 1, 1);
        string xyab = x.substr(0, x.size() - s - 1).append(y.substr(0, y.size() - s - 1));
        chunk->fl = join(computeFingerprint(xyab.c_str(), xyab.size(), *locationPrimeSuite));
        delete millCoords;
        return chunk;
    }

    Worker *workerFactory(string id, Location location) override {
        return new LPTAWorker(id, location, *idPrimeSuite, *locationPrimeSuite, s);
    }

    Server *serverFactory() override {
        return new LPTAServer(idPrimeSuite->size(), idPrimeSuite, s);
    }

    int findWorker(string id) override {
        for (int i = 0; i < workers.size(); i++) {
            auto idfps = computeFingerprint(workers[i]->id.c_str(), workers[i]->id.size(), *idPrimeSuite);
            if (join(idfps) == id) {
                return i;
            }
        }
        return -1;
    }

    ~LPTATest() override {
        delete idPrimeSuite;
        delete locationPrimeSuite;
    }

    string name() override {
        return string("LPTA(s=").append(to_string(s)).append("&pCount=").append(
                to_string(locationPrimeSuite->size())).append(")");
    }
};

#endif //LWPT_LIGHTPRIVATETASKASSIGNMENT_H
